﻿#region

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using DirectShowLib;
using DirectShowLib.SBE;
using MediaSage.Lib.Configuration;
using MediaSage.Lib.Database;
using MediaSage.Lib.Utilities;

#endregion

namespace MediaSage.Lib.Plugin.FileMetadataProvider
{
    public class FileMetadataProvider : PluginBase
    {
        #region Fields

        private readonly MediaInfo MediaInfoObject;

        #endregion Fields

        #region Constructors

        public FileMetadataProvider(Config Configuration, PluginEntity PE)
            : base(Configuration, PE)
        {
            MediaInfoObject = new MediaInfo();
            MediaInfoObject.Option("Internet", "Yes");
            MediaInfoObject.Option("Complete_Get");
            logger.Log("FileMetadata Provider Initialized");
        }

        #endregion Constructors

        #region Methods

        public override bool Run(MediaClass mediaObject)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not applicable for this plugin
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override IList<PersonEntity> SearchPersonByName(string name)
        {
            throw new NotImplementedException();
        }

        public override string[] About()
        {
            VersionInfo verInfo = GetVersionInfo();
            ;
            string[] returnValue =
                {
                    "FileMetadataProvider v" + verInfo.MajorVersion + "." + verInfo.MinorVersion + "." +
                    verInfo.BuildNumber,
                    "Provides metadata information for video formats (mpeg, avi, dvd-rm, wtv, etc)"
                };
            return returnValue;
        }

        public override IList<MediaFileTypesEntity> ExtensionsHandled()
        {
            throw new NotImplementedException();
        }

        public override PluginEntity GetConfiguration()
        {
            throw new NotImplementedException();
        }

        public override MediaClass GetMedia(MediaClass inputMedia, MediaMergeType mergeType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///     This gets the file metadata for the file pointed to by (string)ID
        /// </summary>
        /// <param name="ID"> A string for the file's location </param>
        /// <returns> Updated MediaClass with the file metadata </returns>
        public override MediaClass GetMediaByID(object ID)
        {
            logger.Log("Entering FileMetadataProvider.GetMediaByID(" + ID + ")");
            var filename = (string)ID;
            if (!ReferenceEquals(ID, typeof(string)))
            {
                throw new ArgumentException("TMDBProvider.GetMediaID(ID) must use string for ID");
            }

            if (!isMediaFile(filename))
            {
                logger.Log(filename + " is not a MediaFile");
                logger.Log("Exting FileMetadataProvider.GetMediaByID(" + ID + "), rc==null");
                return null;
            }

            var returnValue = new MediaClass(base.config);

            var metadata = new metadataReturn();

            if (!File.Exists(filename))
            {
                logger.Log(filename + " does not exist or is not readable");
                logger.Log("Exting FileMetadataProvider.GetMediaByID(" + ID + "), rc==null");
                return null;
            }

            string ext = Path.GetExtension(@filename);
            if (ext.ToUpper().Equals(".DVR-MS") || ext.ToUpper().Equals(".WTV"))
            {
                metadata = getMediaCenterMetadata(filename);
            }
            metadata = getFileMetadata(metadata, filename);

            // Now we need to go thru each of the metadata items we got, and assign them to the proper slot in MediaClass
            foreach (DictionaryEntry DE in metadata.generalData)
            {
                switch ((string)DE.Key)
                {
                    case "Duration":
                        long duration;
                        if (long.TryParse((string)DE.Value, out duration))
                        {
                            returnValue.RunTime = new TimeSpan(duration / 60);
                        }
                        break;

                    case "Title":
                        if (DE.Value != null)
                        {
                            returnValue.Title = (string)DE.Value;
                        }
                        break;

                    case "Genre":
                        var tmp = (string)DE.Value;
                        if (tmp != null && tmp.Length > 0)
                        {
                            string[] genres = tmp.Split(';');
                            foreach (string genre in genres)
                            {
                                returnValue.Genre.Add(genre);
                            }
                        }
                        break;

                    case "WM/Language":
                        if (DE.Value != null)
                        {
                            returnValue.Languages.Add((string)DE.Value);
                        }
                        break;

                    case "WM/MediaIsDelay":
                        bool value;
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsDelay = value;
                        }
                        break;

                    case "WM/MediaIsFinale":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsFinale = value;
                        }
                        break;

                    case "WM/MediaIsLive":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsLive = value;
                        }
                        break;

                    case "WM/MediaIsMovie":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsMovie = value;
                        }
                        returnValue.MediaType = EMediaType.MOVIE;
                        break;

                    case "WM/MediaIsPremiere":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsPremire = value;
                        }
                        break;

                    case "WM/MediaIsRepeat":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsRepeat = value;
                        }
                        break;

                    case "WM/MediaIsSAP":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsSAP = value;
                        }
                        break;

                    case "WM/MediaIsSport":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsSport = value;
                        }
                        break;

                    case "WM/MediaIsSubtitled":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsSubtitled = value;
                        }
                        break;

                    case "WM/MediaNetworkAffilation":
                        if (DE.Value != null)
                        {
                            returnValue.NetworkAffiliation = (string)DE.Value;
                        }
                        break;

                    case "WM/MediaOriginalBroadcastDateTime":
                        DateTime date;
                        if (DateTime.TryParse((string)DE.Value, out date))
                        {
                            returnValue.OriginalBroadcastTime = date;
                        }
                        break;

                    case "WM/MediaStationName":
                        if (DE.Value != null)
                        {
                            returnValue.StationName = (string)DE.Value;
                        }
                        break;

                    case "WM/ParentalRating":
                        if (DE.Value != null)
                        {
                            returnValue.ParentalRating = (string)DE.Value;
                        }
                        break;

                    case "WM/SubTitle":
                        if (DE.Value != null)
                        {
                            returnValue.Subtitle = (string)DE.Value;
                        }
                        break;

                    case "WM/SubTitleDescription":
                        if (DE.Value != null)
                        {
                            returnValue.Description = (string)DE.Value;
                        }
                        break;

                    case "WM/WMRVContentProtected":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.ContentProtected = value;
                        }
                        break;

                    case "WM/WMRVHDContent":
                        if (bool.TryParse((string)DE.Value, out value))
                        {
                            returnValue.IsHDContent = value;
                        }
                        break;

                    default:
                        if (DE.Value != null)
                        {
                            var metadataObject = new MetadataEntity();
                            metadataObject.Key = (string)DE.Key;
                            metadataObject.Value = (string)DE.Value;
                            returnValue.Metadata.Add(metadataObject);
                        }
                        break;
                }
            }

            foreach (DictionaryEntry DE in metadata.videoData)
            {
                switch ((string)DE.Key)
                {
                    case "Format":
                        if (DE.Value != null)
                        {
                            returnValue.VideoCodec = (string)DE.Value;
                        }
                        break;

                    case "Format version":
                        if (DE.Value != null)
                        {
                            returnValue.VideoCodecVersion = (string)DE.Value;
                        }
                        break;

                    case "Duration":
                        if (DE.Value != null)
                        {
                            var ts = new TimeSpan();
                            if (TimeSpan.TryParse((string)DE.Value, out ts))
                            {
                                returnValue.VideoLength = ts;
                            }
                        }
                        break;

                    case "Bit rate mode":
                        if (DE.Value != null)
                        {
                            returnValue.VideoBitRateMode = (string)DE.Value;
                        }
                        break;

                    case "Bit rate":
                        float flval;
                        if (float.TryParse((string)DE.Value, out flval))
                        {
                            returnValue.VideoBitrate = flval;
                        }
                        break;

                    case "Width":
                        int intval;
                        if (int.TryParse((string)DE.Value, out intval))
                        {
                            returnValue.VideoWidth = intval;
                        }
                        break;

                    case "Height":
                        if (int.TryParse((string)DE.Value, out intval))
                        {
                            returnValue.VideoHeight = intval;
                        }
                        break;

                    case "Display aspect ratio":
                        if (DE.Value != null)
                        {
                            returnValue.VideoAspect = (string)DE.Value;
                        }
                        break;

                    case "Frame rate":
                        if (DE.Value != null)
                        {
                            if (float.TryParse((string)DE.Value, out flval))
                            {
                                returnValue.VideoFrameRate = flval;
                            }
                        }
                        break;

                    default:
                        if (DE.Value != null)
                        {
                            var metadataObject = new MetadataEntity();
                            metadataObject.Key = (string)DE.Key;
                            metadataObject.Value = (string)DE.Value;
                            returnValue.Metadata.Add(metadataObject);
                        }
                        break;
                }
            }

            return returnValue;
        }

        /// <summary>
        ///     This will get the file metadata for the file pointed to by inputMedia.Filename
        /// </summary>
        /// <param name="inputMedia"> A MediaClass object </param>
        /// <returns> Updated MediaClass with the file metadata </returns>
        public override MediaClass GetUpdateForMedia(MediaClass inputMedia, MediaMergeType MergeType)
        {
            throw new NotImplementedException();
        }

        public override VersionInfo GetVersionInfo()
        {
            VersionInfo verInfo;
            verInfo.Name = Assembly.GetExecutingAssembly().GetName().Name;
            verInfo.MajorVersion = Assembly.GetExecutingAssembly().GetName().Version.Major;
            verInfo.MinorVersion = Assembly.GetExecutingAssembly().GetName().Version.Minor;
            verInfo.BuildNumber = Assembly.GetExecutingAssembly().GetName().Version.Revision;
            return verInfo;
        }

        public override bool Run()
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMedia(MediaClass mediaObject)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMedia(MediaClass mediaObject, int maxReturns)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByName(string name, int maxReturns)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByName(string name)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByNameAndYear(string name, int year)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByNameAndYear(string name, int year, int maxReturns)
        {
            throw new NotImplementedException();
        }

        public override void SetDefaultConfig()
        {
            throw new NotImplementedException();
        }

        private static string GetMetadataItemAsString(IDictionary items, string name)
        {
            var item = (MetadataItem)items[name];
            if (item == null || item.Value == null) return string.Empty;
            return item.Value.ToString().Trim();
        }

        private static object ParseAttributeValue(StreamBufferAttrDataType itemType, byte[] valueData)
        {
            if (!Enum.IsDefined(typeof(StreamBufferAttrDataType), itemType))
                throw new ArgumentOutOfRangeException("itemType");
            if (valueData == null) throw new ArgumentNullException("valueData");

            // Convert the attribute value to a byte array based on the item type.
            switch (itemType)
            {
                case StreamBufferAttrDataType.String:
                    var sb = new StringBuilder(valueData.Length);
                    for (int i = 0; i < valueData.Length - 2; i += 2)
                    {
                        sb.Append(Convert.ToString(BitConverter.ToChar(valueData, i)));
                    }
                    string result = sb.ToString();
                    if (result.EndsWith("\\0")) result = result.Substring(0, result.Length - 2);
                    return result;
                case StreamBufferAttrDataType.Bool:
                    return BitConverter.ToBoolean(valueData, 0);
                case StreamBufferAttrDataType.DWord:
                    return BitConverter.ToInt32(valueData, 0);
                case StreamBufferAttrDataType.QWord:
                    return BitConverter.ToInt64(valueData, 0);
                case StreamBufferAttrDataType.Word:
                    return BitConverter.ToInt16(valueData, 0);
                case StreamBufferAttrDataType.Guid:
                    return new Guid(valueData);
                case StreamBufferAttrDataType.Binary:
                    return valueData;
                default:
                    throw new ArgumentOutOfRangeException("itemType");
            }
        }

        private metadataReturn getFileMetadata(metadataReturn SourceMetadata, string Filename)
        {
            MediaInfoObject.Open(Filename);
            var metaKeysVideo = new[]
                                    {
                                        "Count", "StreamCount", "StreamKind", "StreamKindID", "ID", "Format",
                                        "Format/Info"
                                        , "Format/Url", "Format_Commercial", "Format_Version", "Format_Profile",
                                        "Format_Settings", "Duration", "BitRate", "BitRate_Mode", "Width", "Height",
                                        "PixelAspectRatio", "DisplayAspectRatio", "Rotation/String", "FrameRate_Mode",
                                        "FrameRate", "FrameCount", "Standard", "BitDepth", "ScanType", "StreamSize",
                                        "Title", "Encoded_Application", "Encoded_Library", "Encoded_Library/Name",
                                        "Encoded_Library/Version", "Encoded_Library_Settings", "Language"
                                    };
            var metaKeysAudio = new[]
                                    {
                                        "Count", "StreamCount", "StreamKind", "StreamKindID", "ID", "Format",
                                        "Format/Info"
                                        , "Format/Url", "Format_Commercial", "Format_Version", "Format_Profile",
                                        "Format_Settings", "MuxingMode", "Duration", "BitRate_Mode", "Bitrate",
                                        "Channel(s)", "SamplingRate", "SamplingCount", "BitDepth", "CompressionRatio",
                                        "StreamSize", "Title", "Encoded_Library", "Encoded_Library/Version", "Language",
                                        "Encoded_Date"
                                    };
            var metaKeysGeneral = new[]
                                      {
                                          "Format", "Format/Info", "Format/Extensions", "Format_Commercial",
                                          "Format_Version", "CodecID", "CodecID/Hint", "CodecID/Url",
                                          "CodecID_Description"
                                          , "FileSize", "Duration", "Encoded_Application", "Encoaded_Library",
                                          "Encoded_Library/Name", "Encoded_Library/Version", "Cropped"
                                      };
            foreach (string key in metaKeysVideo)
            {
                string val = MediaInfoObject.Get(StreamKind.Video, 0, key);
                SourceMetadata.videoData.Add(key, val);
                logger.Log("Key: " + key + " found in Video metadata, value=" + val);
            }
            foreach (string key in metaKeysAudio)
            {
                string val = MediaInfoObject.Get(StreamKind.Audio, 0, key);
                SourceMetadata.audioData.Add(key, val);
                logger.Log("Key: " + key + " found in Audio metadata, value=" + val);
            }
            foreach (string key in metaKeysGeneral)
            {
                string val = MediaInfoObject.Get(StreamKind.General, 0, key);
                SourceMetadata.generalData.Add(key, val);
                logger.Log("Key: " + key + " found in General metadata, value=" + val);
            }
            MediaInfoObject.Close();

            logger.Log("Exiting FileMetadataProvider.getFileMetadata");
            return SourceMetadata;
        }

        private metadataReturn getMediaCenterMetadata(string filename)
        {
            logger.Log("Entering getMediaCenterMetadata(" + filename + ")");
            var retVal = new metadataReturn();
            retVal.audioData = new Hashtable();
            retVal.videoData = new Hashtable();
            retVal.generalData = new Hashtable();

            var dmde = new DvrmsMetadataEditor(filename);
            IDictionary attributes = dmde.GetAttributes(true);
            retVal.generalData.Add("WM/MediaCredits", GetMetadataItemAsString(attributes, "WM/MediaCredits"));
            retVal.generalData.Add("WM/Genre", GetMetadataItemAsString(attributes, "WM/Genre"));
            retVal.generalData.Add("WM/WRVActualSoftPostPadding",
                                   GetMetadataItemAsString(attributes, "WM/WRVActualSoftPostPadding"));
            retVal.generalData.Add("WM/OriginalReleaseTime",
                                   GetMetadataItemAsString(attributes, "WM/OriginalReleaseTime"));
            retVal.generalData.Add("WM/ProviderCopyright", GetMetadataItemAsString(attributes, "WM/ProviderCopyright"));
            retVal.generalData.Add("WM/MediaIsSubtitled", GetMetadataItemAsString(attributes, "WM/MediaIsSubtitled"));
            retVal.generalData.Add("WM/WMRVHDContent", GetMetadataItemAsString(attributes, "WM/WMRVHDContent"));
            retVal.generalData.Add("WM/MediaNetworkAffiliation",
                                   GetMetadataItemAsString(attributes, "WM/MediaNetworkAffiliation"));
            retVal.generalData.Add("Title", GetMetadataItemAsString(attributes, "Title"));
            retVal.generalData.Add("WM/MediaIsRepeat", GetMetadataItemAsString(attributes, "WM/MediaIsRepeat"));
            string tmpVal = GetMetadataItemAsString(attributes, "WM/WMRVEndTime");
            long tmpNum;
            DateTime tmpDate;
            try
            {
                tmpNum = Convert.ToInt64(tmpVal);
                tmpDate = new DateTime(tmpNum);
                retVal.generalData.Add("WM/WMRVEndTime", tmpDate.ToString());
            }
            catch (Exception)
            {
            }

            retVal.generalData.Add("WM/VideoClosedCaptioning",
                                   GetMetadataItemAsString(attributes, "WM/VideoClosedCaptioning"));
            retVal.generalData.Add("WM/MediaIsSAP", GetMetadataItemAsString(attributes, "WM/MediaIsSAP"));
            retVal.generalData.Add("WM/WMRVContentProtectedPercent",
                                   GetMetadataItemAsString(attributes, "WM/WMRVContentProtectedPercent"));
            retVal.generalData.Add("WM/WMRVWatched", GetMetadataItemAsString(attributes, "WM/WMRVWatched"));
            retVal.generalData.Add("WM/MediaStationName", GetMetadataItemAsString(attributes, "WM/MediaStationName"));
            retVal.generalData.Add("WM/Language", GetMetadataItemAsString(attributes, "WM/Language"));
            retVal.generalData.Add("WM/SubTitleDescription",
                                   GetMetadataItemAsString(attributes, "WM/SubTitleDescription"));
            tmpVal = GetMetadataItemAsString(attributes, "WM/WMRVEncodeTime");
            try
            {
                tmpNum = Convert.ToInt64(tmpVal);
                tmpDate = new DateTime(tmpNum);
                retVal.generalData.Add("WM/WMRVEncodeTime", tmpDate.ToString());
            }
            catch (Exception)
            {
            }
            retVal.generalData.Add("WM/MediaIsFinale", GetMetadataItemAsString(attributes, "WM/MediaIsFinale"));
            retVal.generalData.Add("WM/MediaIsDelay", GetMetadataItemAsString(attributes, "WM/MediaIsDelay"));
            retVal.generalData.Add("WM/MediaStationCallSign",
                                   GetMetadataItemAsString(attributes, "WM/MediaStationCallSign"));
            retVal.generalData.Add("WM/SubTitle", GetMetadataItemAsString(attributes, "WM/SubTitle"));
            retVal.generalData.Add("WM/WMRVContentProtected",
                                   GetMetadataItemAsString(attributes, "WM/WMRVContentProtected"));
            retVal.generalData.Add("WM/MediaOriginalChannel",
                                   GetMetadataItemAsString(attributes, "WM/MediaOriginalChannel"));
            retVal.generalData.Add("WM/MediaOriginalBroadcastDateTime",
                                   GetMetadataItemAsString(attributes, "WM/MediaOriginalBroadcastDateTime"));
            retVal.generalData.Add("WM/WMRVDTVContent", GetMetadataItemAsString(attributes, "WM/WMRVDTVContent"));
            retVal.generalData.Add("WM/MediaIsPremiere", GetMetadataItemAsString(attributes, "WM/MediaIsPremiere"));

            retVal.generalData.Add("WM/MediaOriginalRunTime",
                                   GetMetadataItemAsString(attributes, "WM/MediaOriginalRunTime"));
            retVal.generalData.Add("WM/ParentalRating", GetMetadataItemAsString(attributes, "WM/ParentalRating"));

            var media = new AMMediaType();
            media.majorType = MediaType.Video;
            media.subType = MediaSubType.RGB24;
            media.formatType = FormatType.VideoInfo;
            var sg = (ISampleGrabber)new SampleGrabber();

            sg.SetMediaType(media);
            DsUtils.FreeAMMediaType(media);
            media = null;

            var gb = (IGraphBuilder)new FilterGraph();
            var mp = (IMediaPosition)gb;
            gb.AddFilter((IBaseFilter)sg, "SampleGrabber");
            var bv = (IBasicVideo)gb;
            gb.RenderFile(filename, null);

            int videoWidth, videoHeight;
            bv.GetVideoSize(out videoWidth, out videoHeight);
            retVal.videoData.Add("Width", videoWidth.ToString());
            retVal.videoData.Add("Height", videoHeight.ToString());
            int bitrate;
            bv.get_BitRate(out bitrate);
            retVal.videoData.Add("bitrate", videoHeight.ToString());

            logger.Log("Exiting getMediaCenterMetadata(" + filename + ")");
            return (retVal);
        }

        private bool isMediaFile(string filename)
        {
            if (filename == null)
            {
                return (false);
            }
            string extension = Path.GetExtension(@filename);
            foreach (MediaFileTypesEntity ft in config.MediaTypes)
            {
                string tft = "." + ft.Extension;
                if (tft.ToUpper().Equals(extension.ToUpper()))
                {
                    logger.Log(filename + " is a media file");
                    return (true);
                }
            }
            logger.Log(filename + " is NOT a media file");
            return (false);
        }

        #endregion Methods

        #region Nested Types

        private struct metadataReturn
        {
            #region Properties

            public Hashtable audioData { get; set; }

            public Hashtable generalData { get; set; }

            public Hashtable videoData { get; set; }

            #endregion Properties
        }

        #endregion Nested Types
    }
}